﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Data.Objects;


using Sita.Entities;
using Sita.Data.Core;
using Sita.Data.Core.Specification;

namespace Sita.Data.Repositories
{
    public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, IObjectWithChangeTracker, new()
    {
        IQueryableContext m_Context;

        public GenericRepository(IQueryableContext context)
        {
            m_Context = context;
        }

        public IContext StoreContext
        {
            get
            {
                return m_Context as IContext;
            }
        }

        #region IGenericRepository<TEntity> Members

        public virtual IQueryable<TEntity> GetQuery()
        {
            throw new NotImplementedException();
        }

        public virtual IEnumerable<TEntity> GetAll()
        {
            return m_Context.CreateObjectSet<TEntity>().ToList();
        }

        public virtual IEnumerable<TEntity> GetBySpec(ISpecification<TEntity> spec)
        {
            return m_Context.CreateObjectSet<TEntity>().Where(spec.SatisfiedBy()).ToList();
        }

        public virtual IEnumerable<TEntity> GetBySpec<S>(ISpecification<TEntity> spec, Expression<Func<TEntity, S>> orderByExpression, bool ascending)
        {
            if (ascending)
                return m_Context.CreateObjectSet<TEntity>().Where(spec.SatisfiedBy()).OrderBy(orderByExpression).ToList();
            else
                return m_Context.CreateObjectSet<TEntity>().Where(spec.SatisfiedBy()).OrderByDescending(orderByExpression).ToList();
        }

        public virtual IEnumerable<TEntity> GetAllPaginated<S>(int pageIndex, int pageSize, ISpecification<TEntity> spec, Expression<Func<TEntity, S>> orderByExpression, bool ascending, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Where(spec.SatisfiedBy()).Count();

            return (ascending)
                                ?
                                    ReturnSet.Where(spec.SatisfiedBy()).OrderBy(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .ToList()
                                :
                                    ReturnSet.Where(spec.SatisfiedBy()).OrderByDescending(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .ToList();
        }

        public virtual IEnumerable<TEntity> GetAllPaginated<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderByExpression, bool ascending, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Count();

            return (ascending)
                                ?
                                    ReturnSet.OrderBy(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .ToList()
                                :
                                    ReturnSet.OrderByDescending(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .ToList();
        }

        public virtual IEnumerable<TEntity> GetAllPaginated(int pageIndex, int pageSize, ISpecification<TEntity> spec, string orderColumn, bool ascending, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;
            RepositoryHelper<TEntity> Helper;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Count();

            Helper = new RepositoryHelper<TEntity>();

            var OrderByExpr = Helper.GetOrderExpression(orderColumn);

            if (spec == null)
            {
                return GetAllPaginated(pageIndex, pageSize, OrderByExpr, ascending, out totalRows);
            }
            else
            {
                return GetAllPaginated(pageIndex, pageSize, spec, OrderByExpr, ascending, out totalRows);
            }
        }

        public virtual IEnumerable<TEntity> GetAllPaginated(int pageIndex, int pageSize, string orderColumn, bool ascending, out int totalRows)
        {
            return GetAllPaginated(pageIndex, pageSize, null, orderColumn, ascending, out totalRows);
        }

        public virtual IEnumerable<TEntity> GetAllPaginated<S>(int pageIndex, int pageSize, ISpecification<TEntity> spec, Expression<Func<TEntity, S>> orderByExpression, bool ascending, string include, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Where(spec.SatisfiedBy()).Count();

            return (ascending)
                                ?
                                    ReturnSet.Where(spec.SatisfiedBy()).OrderBy(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .Include(include)
                                     .ToList()
                                :
                                    ReturnSet.Where(spec.SatisfiedBy()).OrderByDescending(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .Include(include)
                                     .ToList();
        }

        public virtual IEnumerable<TEntity> GetAllPaginatedWithInclude<S>(int pageIndex, int pageSize, ISpecification<TEntity> spec, Expression<Func<TEntity, S>> orderByExpression, bool ascending, string include, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Where(spec.SatisfiedBy()).Count();

            return (ascending)
                                ?
                                    ReturnSet.Where(spec.SatisfiedBy()).OrderBy(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .Include(include)
                                     .ToList()
                                :
                                    ReturnSet.Where(spec.SatisfiedBy()).OrderByDescending(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .Include(include)
                                     .ToList();
        }

        public virtual IEnumerable<TEntity> GetAllPaginatedWithInclude<S>(int pageIndex, int pageSize, Expression<Func<TEntity, S>> orderByExpression, bool ascending, string include, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Count();

            return (ascending)
                                ?
                                    ReturnSet.OrderBy(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .Include(include)
                                     .ToList()
                                :
                                    ReturnSet.OrderByDescending(orderByExpression)
                                     .Skip(pageIndex * pageSize)
                                     .Take(pageSize)
                                     .Include(include)
                                     .ToList();
        }

        public virtual IEnumerable<TEntity> GetAllPaginatedWithInclude(int pageIndex, int pageSize, ISpecification<TEntity> spec, string orderColumn, bool ascending, string include, out int totalRows)
        {
            IObjectSet<TEntity> ReturnSet;
            RepositoryHelper<TEntity> Helper;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            totalRows = ReturnSet.Count();

            Helper = new RepositoryHelper<TEntity>();

            var OrderByExpr = Helper.GetOrderExpression(orderColumn);

            if (spec == null)
            {
                return GetAllPaginatedWithInclude(pageIndex, pageSize, OrderByExpr, ascending, include, out totalRows);
            }
            else
            {
                return GetAllPaginatedWithInclude(pageIndex, pageSize, spec, OrderByExpr, ascending, include, out totalRows);
            }
        }

        public virtual IEnumerable<TEntity> GetAllPaginatedWithInclude(int pageIndex, int pageSize, string orderColumn, bool ascending, string include, out int totalRows)
        {
            return GetAllPaginatedWithInclude(pageIndex, pageSize, null, orderColumn, ascending, include, out totalRows);
        }

        public virtual IEnumerable<TEntity> GetAllSorted(string orderColumn, bool ascending)
        {
            System.Data.Objects.IObjectSet<TEntity> ReturnSet;
            RepositoryHelper<TEntity> Helper;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            Helper = new RepositoryHelper<TEntity>();

            var OrderByExpr = Helper.GetOrderExpression(orderColumn);

            return GetAllSorted(OrderByExpr, ascending);
        }

        public virtual IEnumerable<TEntity> GetAllSorted<S>(Expression<Func<TEntity, S>> orderByExpression, bool ascending)
        {
            System.Data.Objects.IObjectSet<TEntity> ReturnSet;

            ReturnSet = m_Context.CreateObjectSet<TEntity>();

            return (ascending)
                                ?
                                    ReturnSet.OrderBy(orderByExpression).ToList()
                                :
                                    ReturnSet.OrderByDescending(orderByExpression).ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(Expression<Func<TEntity, bool>> filter)
        {
            return m_Context.CreateObjectSet<TEntity>().Where(filter);
        }

        public virtual TEntity GetByID(int id)
        {
            System.Data.EntityKey EntityKeyData;
            IEnumerable<KeyValuePair<string, object>> EntityKeyValues;
            string FieldKeyName;
            ObjectSet<TEntity> GenericSet;

            GenericSet = (ObjectSet<TEntity>)this.m_Context.CreateObjectSet<TEntity>();

            if (GenericSet.EntitySet.ElementType.KeyMembers.Count > 0)
            {
                FieldKeyName = GenericSet.EntitySet.ElementType.KeyMembers[0].Name;

                EntityKeyValues = new KeyValuePair<string, object>[] { new KeyValuePair<string, object>(FieldKeyName, id) };

                EntityKeyData = new System.Data.EntityKey(this.m_Context.DefaultContainerName + "." + GenericSet.EntitySet.Name, EntityKeyValues);

                return (TEntity)this.m_Context.GetObjectByKey(EntityKeyData);
            }
            return null;
        }

        public virtual bool Modify(TEntity item)
        {
            if (item.ChangeTracker != null)
                item.MarkAsModified();

            m_Context.SetChanges<TEntity>(item);

            return true;
        }

        public virtual void Add(TEntity entity)
        {
            if (entity.ChangeTracker != null)
                entity.MarkAsAdded();

            this.m_Context.CreateObjectSet<TEntity>().AddObject(entity);
        }

        public virtual void Delete(TEntity entity)
        {

            if (entity.ChangeTracker != null)
                entity.MarkAsDeleted();
            this.m_Context.CreateObjectSet<TEntity>().DeleteObject(entity);
        }

        public virtual void Delete(IList<TEntity> entity)
        {
            for (int i = 0; i < entity.Count; i++)
            {
                if (entity[i].ChangeTracker != null)
                    entity[i].MarkAsDeleted();
                this.m_Context.CreateObjectSet<TEntity>().DeleteObject(entity[i]);

            }
        }

        #endregion

        public virtual bool Duplicate(TEntity entity)
        {
            //this.m_Context.Dettach(entity);

            this.m_Context.CreateObjectSet<TEntity>().AddObject(DataContractSerialization(entity));

            //this.m_Context.CreateObjectSet<TEntity>().

            return true;
        }

        private static TEntity DataContractSerialization(TEntity obj)
        {
            System.Runtime.Serialization.DataContractSerializer dcSer = new System.Runtime.Serialization.DataContractSerializer(obj.GetType());
            System.IO.MemoryStream memoryStream = new System.IO.MemoryStream();
            dcSer.WriteObject(memoryStream, obj);
            memoryStream.Position = 0;
            TEntity newObject = (TEntity)dcSer.ReadObject(memoryStream);
            return newObject;
        }
    }
}
